/*!
 * js模拟系统select v1.0
 * http://www.cnblogs.com/typeof/ 
 * 
 * 主流浏览器对html的select元素渲染都不一样，IE系列(6, 7, 8)也不一样，
 * firefox，chrome，safari，opera 渲染和事件处理也稍有差异
 * 该脚本解决了在不同浏览器下渲染和事件响应不一致的问题，对系统select是完全
 * 意义上的替换。v1.0版本只支持单个select选择即不支持二级或者三级联动且不支持系统select的onchange事件。
 * 该版本支持模拟select选择的数据和系统select选中数据的同步，不影响form表单的提交 
 *
 * 如果page上select有的不想通过该脚本替换，只想维持系统select，可以在相应的select元素添加自定义属性data-enabled="true"，
 * 否则可以在想要通过该脚本替换的select元素上添加自定义属性data-enabled="false"或者不加，会默认为这个select需要
 * 通过该脚本进行替换
 *
 * 日期：2012-11-07 15:38
 */
(function (squid) {
  function JSelect() {
    this.init()
  }

  JSelect.prototype = {
    constructor: JSelect,
    init: function (context) {
      //获取指定上下文所有select元素
      var elems = squid.getElementsByTagName('select', context)
      this.globalEvent()
      this.initView(elems)
    },
    initView: function (elems) {
      var i = 0,
        elem,
        length = elems.length,
        enabled;

      for (; i < length; i++) {
        elem = elems[i]
        enabled = elem.getAttribute('data-enabled')
        //使用系统select
        if (!enabled || enabled === 'true')
          continue
        if (squid.isVisible(elem))
          elem.style.display = 'none'

        this.create(elem)
      }
    },
    create: function (elem) {
      var data = [],
        i = 0,
        length,
        option,
        options,
        value,
        text,
        obj,
        lis,
        ul,
        _default,
        icon,
        selectedText,
        selectedValue,
        div,
        wrapper,
        position,
        left,
        top,
        cssText;

      options = elem.getElementsByTagName('option')
      length = options.length
      for (; i < length; i++) {
        option = options[i]
        value = option.value
        text = option.innerText || option.textContent

        obj = {
          value: value,
          text: text
        }
        if (option.selected) {
          selectedValue = value
          selectedText = text
          obj['selected'] = true
        }
        data.push(obj)
      }

      lis = this.render(this.tmpl, data)
      ul = '<ul class="select-item">' + lis + '</ul>'
      //
      div = document.createElement('span')
      div.style.display = 'none'
      div.className = 'select-wrapper'
      //已选元素
      _default = document.createElement('span')
      _default.className = 'select-default unselectable'
      _default.unselectable = 'on'
      //让div元素能够获取焦点
      _default.setAttribute('tabindex', '1')
      _default.setAttribute('data-value', selectedValue)
      _default.setAttribute('hidefocus', true)
      _default.innerHTML = selectedText
      div.appendChild(_default)
      //选择icon
      icon = document.createElement('i')
      icon.className = 'select-icon'
      div.appendChild(icon)
      //下拉列表
      wrapper = document.createElement('span')
      wrapper.className = 'select-list hide'
      wrapper.innerHTML = ul
      //生成新的元素
      div.appendChild(wrapper)
      //插入到select元素后面
      elem.parentNode.insertBefore(div, null)
      //获取select元素left top值
      //先设置select显示，取完left, top值后重新隐藏
      elem.style.display = 'block'
      //事件绑定
      this.sysEvent(div)
      position = squid.position(elem)
      elem.style.display = 'none'
      left = position.left
      top = position.top
      cssText = ' display: inline-block;'
      div.style.cssText = cssText
    },
    globalEvent: function () {
      //document 添加click事件，用户处理每个jselect元素展开关闭
      var target,
        className,
        elem,
        wrapper,
        status,
        that = this;


      squid.on(document, 'click', function (event) {
        target = event.target,
          className = target.className;

        switch (className) {
          case 'select-icon':
          case 'select-default unselectable':
            elem = target.tagName.toLowerCase() === 'span' ? target : target.previousSibling
            wrapper = elem.nextSibling.nextSibling

            //firefox 鼠标右键会触发click事件
            //鼠标左键点击执行
            if (event.button === 0) {
              //初始化选中元素
              that.initSelected(elem)
              if (squid.isHidden(wrapper)) {
                status = 'inline-block'
                //关闭所有展开jselect
                that.closeSelect()
              } else {
                status = 'none'
              }
              wrapper.style.display = status
              elem.focus()
            } else if (event.button === 2) {
              wrapper.style.display = 'none'
            }
            that.zIndex(wrapper)
            break
          case 'select-option':
          case 'select-option selected':
            if (event.button === 0) {
              that.fireSelected(target, target.parentNode.parentNode.previousSibling.previousSibling)
              wrapper.style.display = 'none'
            }
            break
          default:
            while (target && target.nodeType !== 9) {
              if (target.nodeType === 1) {
                if (target.className === 'select-wrapper') {
                  return
                }
              }
              target = target.parentNode
            }
            that.closeSelect()
            break
        }
      })
    },
    sysEvent: function (elem) {
      var stand = elem.firstChild,
        dropdown = elem.lastChild,
        target,
        //firefox = 'MozBinding' in document.documentElement.style,
        chrome = /chrome/i.test(navigator.userAgent),
        keyup = chrome ? 'keypress' : 'keyup',
        that = this;

      squid.on(elem, 'mouseover', function (event) {
        if (!that.doScrolling) {
          target = event.target
          that.activate(target)
        }
      })

      squid.on(elem, 'mouseout', function (event) {
        if (!that.doScrolling) {
          target = event.target
          that.deactivate(target)
        }
      })

      squid.on(stand, 'keydown', function (event) {
        var keyCode = event.keyCode;

        switch (keyCode) {
          //回车选中
          case 13:
            that.enter(dropdown)
            break
          //向上键
          case 38:
            that.doScrolling = true
            that.up(dropdown)
            break
          //向下键
          case 40:
            that.doScrolling = true
            that.down(dropdown)
            break
          default:
            break
        }
      })

      squid.on(stand, keyup, function (event) {
        var keyCode = event.keyCode;

        switch (keyCode) {
          //回车选中
          case 13:
            that.doScrolling = false
            break
          //向上键
          case 38:
            that.doScrolling = false
            break
          //向下键
          case 40:
            that.doScrolling = false
            break
          default:
            break
        }
      })
    },
    zIndex: function (elem) {
      var index = 10,
        cur = elem.parentNode.parentNode,
        next = squid.next(cur);

      if (next) {
        cur.style.zIndex = index
        next.style.zIndex = --index
      }
    },
    initSelected: function (elem) {
      var curText = elem.innerText || elem.textContent,
        curValue = elem.getAttribute('data-value'),
        wrapper = elem.nextSibling.nextSibling,
        n = wrapper.firstChild.firstChild,
        text,
        value,
        dir,
        min = 0,
        max,
        hidden = false;

      for (; n; n = n.nextSibling) {
        text = n.innerText || n.textContent
        value = n.getAttribute('data-value')
        if (curText === text && curValue === value) {
          //显示已选中元素
          if (squid.isHidden(wrapper)) {
            wrapper.style.display = 'block'
            hidden = true
          }
          max = wrapper.scrollHeight
          if (n.offsetTop > (max / 2)) {
            if (wrapper.clientHeight + wrapper.scrollTop === max)
              dir = 'up'
            else
              dir = 'down'
          } else {
            if (wrapper.scrollTop === min)
              dir = 'down'
            else
              dir = 'up'
          }
          this.inView(n, wrapper, dir)
          if (hidden)
            wrapper.style.display = 'none'
          this.activate(n)
          break
        }
      }
    },
    activate: function (elem) {
      var tagName = (elem.tagName || '').toLowerCase(),
        className = elem.className,
        parent = elem.parentNode,
        first = parent.firstChild,
        last = parent.lastChild;

      switch (tagName) {
        case 'li':
          //li.select-option 元素
          if (!~className.indexOf('selected') && (elem !== first || elem !== last)) {
            this.deactivate(elem)
            elem.className = className + ' selected'
          }
          break
        default:
          break
      }
    },
    deactivate: function (elem) {
      var tagName = (elem.tagName || '').toLowerCase(),
        className = (' ' + elem.className + ' ').replace(/[\n\r\t]/, '');

      switch (tagName) {
        case 'li':
          //li.select-option 元素
          var i = 0,
            lis = squid.siblings(elem),
            length = lis.length,
            cur;

          for (; i < length; i++) {
            cur = lis[i]
            cur.className = squid.trim(className.replace(' selected ', ''))
          }
          break
        default:
          break
      }
    },
    fireSelected: function (elem, s) {
      var text = elem.innerText || elem.textContent,
        value = elem.getAttribute('data-value'),
        r;

      s.setAttribute('data-value', value)
      if (s.innerText)
        s.innerText = text
      else
        s.textContent = text

      //触发系统select选中，用于form表单提交
      r = s.parentNode.previousSibling.previousSibling
      r.value = value
      r.setAttribute('data-text', text)
    },
    closeSelect: function () {
      var elems = squid.getElementsByClassName('select-list'),
        i = 0,
        length = elems.length,
        elem;

      for (; i < length; i++) {
        elem = elems[i]
        if (squid.isVisible(elem))
          elem.style.display = 'none'
      }
    },
    up: function (elem) {
      var ul = elem.firstChild,
        lis = ul.childNodes,
        li = this.getSelectedIndex(lis),
        cur,
        i = li.index;

      if (i > 0) {
        i--
        cur = lis[i]
        //判断元素是否in view
        this.inView(cur, elem, 'up')
        this.activate(cur)
        this.fireSelected(cur, elem.previousSibling.previousSibling)
      }
    },
    down: function (elem) {
      var ul = elem.firstChild,
        lis = ul.childNodes,
        li = this.getSelectedIndex(lis),
        cur,
        i = li.index;

      if (i < lis.length - 1) {
        i++
        cur = lis[i]
        //判断元素是否in view
        this.inView(cur, elem, 'down')
        this.activate(cur)
        this.fireSelected(cur, elem.previousSibling.previousSibling)
      }
    },
    enter: function (elem) {
      var ul = elem.firstChild,
        lis = ul.childNodes,
        li,
        i,
        cur;

      li = this.getSelectedIndex(lis)
      i = li.index
      cur = lis[i]
      this.fireSelected(cur, elem.previousSibling.previousSibling)
      this.closeSelect()
    },
    getSelectedIndex: function (elems) {
      var i = 0,
        length = elems.length,
        elem;

      for (; i < length; i++) {
        elem = elems[i]
        if (~elem.className.indexOf('selected')) {
          return {
            index: i
          }
        }
      }

      return {
        index: -1
      }
    },
    inView: function (elem, wrapper, dir) {
      var scrollTop = wrapper.scrollTop,
        offsetTop = elem.offsetTop,
        top;

      if (dir === 'up') {
        if (offsetTop === 0) {
          wrapper.scrollTop = offsetTop;
        } else if (offsetTop < scrollTop) {
          top = offsetTop - scrollTop
          this.scrollInView(wrapper, top)
        }
      } else {
        var clientHeight = wrapper.clientHeight;

        if (offsetTop + elem.offsetHeight === wrapper.scrollHeight) {
          wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
        } else if (offsetTop + elem.offsetHeight > clientHeight + scrollTop) {
          top = (offsetTop + elem.offsetHeight) - (scrollTop + clientHeight)
          this.scrollInView(wrapper, top)
        }
      }
    },
    scrollInView: function (elem, top) {
      setTimeout(function () {
        elem.scrollTop += top
      }, 10)
    },
    doScrolling: false,
    render: function (tmpl, data) {
      var i = 0,
        cur,
        length = data.length,
        prop,
        value,
        item,
        r = [];

      for (; i < length; i++) {
        cur = data[i]
        item = tmpl.replace(/\{\{\w+\}\}/g, function (a) {
          prop = a.replace(/[\{\}]+/g, '')
          value = cur[prop] || ''
          if (prop === 'class') {
            value += 'select-option'
            if (cur.selected) {
              value += ' selected'
            }
          }

          return value
        })
        r.push(item)
      }

      return r.join('')
    },
    tmpl: '<li class="{{class}}" data-value="{{value}}">{{text}}</li>'
  }

  squid.swing.jselect = function () {
    return new JSelect()
  }
})(squid);